package pri.hillchen.springbootstd.bms.common.util;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;

/**
 * 验证码生成工具
 * Created by hillchen on 2017/11/2.
 */
public class ValidateCode {
    /**
     *图片默认的宽度
      */
    public static final int DEF_CODE_WIDTH = 80;
    /**
     *图片默认的高度。
     */
    public static final int DEF_CODE_HEIGHT = 40;
    /**
     * 验证码字符默认个数
     */
    public static final int DEF_CODE_COUNT = 4;
    /**
     *验证码干扰线数
     */
    public static final int DEF_LINE_COUNT = 200;

    /**
     *验证码干扰点
     */
    public static final float DEF_YAWP_RATE= 0.1f;

    public static final int COLOR_RGB_RANGE = 255;

    private static final Random RANDOM = new Random();

    /**
     * 验证码范围,去掉0(数字)和O(拼音)容易混淆的(小写的1和L也可以去掉,大写不用了)
     */
    private static final char[] CODE_SEQUENCE = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
            'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9'};

    private static final int SEQUENCE_COUNT = CODE_SEQUENCE.length;


    /**
     */
    public static CodeInfo createValidateCode(CodeBuilder codeBuilder) {
        CodeInfo codeInfo = codeBuilder.builder();
        createCodeImg(codeInfo);
        return codeInfo;
    }

    public static CodeInfo createValidateCode() {
        return createValidateCode(new CodeBuilder());
    }

    public static char[] createCodes(int codeCount){
        if(codeCount <= 0){
            codeCount = DEF_CODE_COUNT;
        }
        char[] retCodes = new  char[codeCount];
        for(int i=0;i<codeCount;i++){
            retCodes[i] = CODE_SEQUENCE[RANDOM.nextInt(SEQUENCE_COUNT)];
        }
        return retCodes;
    }

    public static void createCodeImg(CodeInfo codeInfo){
        int imgWidth = codeInfo.imgWidth;
        int imgHeight = codeInfo.imgHeight;

        BufferedImage bufferedImage = new BufferedImage(imgWidth,imgHeight,BufferedImage.TYPE_INT_RGB);
        codeInfo.setBufferedImage(bufferedImage);

        Graphics2D codeGraphics2D = bufferedImage.createGraphics();
        codeGraphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);

        /**
         * 随机生成图片背景色并画出背景图片
         */
        Color backgroundColor = randomColor(200, 255);
        codeGraphics2D.setColor(backgroundColor);
        codeGraphics2D.fillRect(0, 2, imgWidth, imgHeight - 4);

        /**
         *绘制干扰物
         */
        drawDisturbs(codeInfo,codeGraphics2D);
        /**
         * 绘制噪点
         */
        drawYawp(codeInfo);

        int codeSize = codeInfo.codeChars.length;
        /**
         * 每个字符的宽度(左右各空出一个字符)
         */
        int fontWidth = (imgWidth - 10) /codeSize;
        /**
         * 字体高度
         */
        int fontHeigth = imgHeight - 5;
        /**
         *创建字体,可以修改为其它的
         */
        Font font = new Font("Algerian", Font.ITALIC, fontHeigth);
        codeGraphics2D.setFont(font);
        /**
         *
         *画出验证码
         */
        for (int i=0;i<codeSize;i++) {

            Color fontColor = randomColor(0, 150);
            codeGraphics2D.setColor(fontColor);
            AffineTransform affine = randomAffin(i,fontWidth,fontHeigth);
            codeGraphics2D.setTransform(affine);
            codeGraphics2D.drawChars(codeInfo.codeChars, i, 1, fontWidth * i + 5, imgHeight/2 + fontHeigth/2 - 10);


        }

    }

    private static AffineTransform randomAffin(int index,int fontWidth,int fontHeigth){
        AffineTransform affine = new AffineTransform();
        affine.setToRotation(Math.PI / 4 * RANDOM.nextDouble() * (RANDOM.nextBoolean() ? 1 : -1),
                fontWidth * index + fontHeigth/2, fontHeigth/2);
        return affine;
    }

    /**
     * 绘制干扰线
     * @param codeInfo
     * @param codeGraphics2D
     */
    private static void drawDisturbs(CodeInfo codeInfo,Graphics2D codeGraphics2D){
        /**
         * 画出干扰线
         */
        for (int i = 0;i<codeInfo.lineCout;i++) {
            /**
             *设置随机开始和结束坐标
             */
            int xs = RANDOM.nextInt(codeInfo.imgWidth);
            int ys = RANDOM.nextInt(codeInfo.imgHeight);
            int xe = xs + RANDOM.nextInt(codeInfo.imgWidth / 8);
            int ye = ys + RANDOM.nextInt(codeInfo.imgHeight / 8);

            // 产生随机的颜色值，让输出的每个干扰线的颜色值都将不同。
            codeGraphics2D.setColor(randomColor(160, 200));
            codeGraphics2D.drawLine(xs, ys, xe, ye);
        }
    }

    /**
     * 绘制噪点
     * @param codeInfo
     */
    private static void drawYawp(CodeInfo codeInfo){
        int yawpNodes = (int)codeInfo.yawpRate * codeInfo.imgWidth*codeInfo.imgHeight;
        for(int i =0 ; i< yawpNodes; i++){
            int x = RANDOM.nextInt(codeInfo.imgWidth);
            int y = RANDOM.nextInt(codeInfo.imgHeight);
            codeInfo.getBufferedImage().setRGB(x,y,randomColor(0,255).getRGB());
        }
    }

    /**
     * 随机生成颜色
     * @param fc
     * @param bc
     * @return
     */
    public static Color randomColor(int fc,int bc){
        if (fc > COLOR_RGB_RANGE){
            fc = COLOR_RGB_RANGE;
        }
        if (fc < 0 ){
            fc = 0;
        }
        if (bc > COLOR_RGB_RANGE){
            bc = COLOR_RGB_RANGE;
        }
        if (bc < 0 ){
            bc = 0;
        }
        return new Color(randomIndex(fc,bc), randomIndex(fc,bc),
                randomIndex(fc,bc));
    }

    /**
     * 生成指定区间的随机数
     * @param fc
     * @param bc
     * @return
     */
    public static int randomIndex(int fc,int bc){
        return fc + RANDOM.nextInt(bc - fc);
    }

    /**
     *  使图片扭曲
     * @param codeInfo
     */
    public static void shear(CodeInfo codeInfo,Graphics2D codeGraphics2D,Color backgroundColor){
        shearX(codeInfo, codeGraphics2D, backgroundColor);
        shearY(codeInfo, codeGraphics2D, backgroundColor);
    }
    private static void shearX(CodeInfo codeInfo,Graphics2D codeGraphics2D,Color backgroundColor) {

        int period = RANDOM.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = RANDOM.nextInt(2);

        for (int i = 0; i < codeInfo.imgHeight; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            codeGraphics2D.copyArea(0, i, codeInfo.imgWidth, 1, (int) d, 0);
            if (borderGap) {
                codeGraphics2D.setColor(backgroundColor);
                codeGraphics2D.drawLine((int) d, i, 0, i);
                codeGraphics2D.drawLine((int) d + codeInfo.imgWidth, i, codeInfo.imgWidth, i);
            }
        }

    }

    private static void shearY(CodeInfo codeInfo,Graphics2D codeGraphics2D,Color backgroundColor) {

        int period = RANDOM.nextInt(40) + 10; // 50;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < codeInfo.imgWidth; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862D * (double) phase)
                    / (double) frames);
            codeGraphics2D.copyArea(i, 0, 1, codeInfo.imgHeight, 0, (int) d);
            if (borderGap) {
                codeGraphics2D.setColor(backgroundColor);
                codeGraphics2D.drawLine(i, (int) d, i, 0);
                codeGraphics2D.drawLine(i, (int) d + codeInfo.imgHeight, i, codeInfo.imgHeight);
            }

        }

    }


    public static class CodeInfo{
        private int imgWidth;
        private int imgHeight;
        private char[] codeChars;
        private int lineCout;
        private float yawpRate;
        private BufferedImage bufferedImage;

        public CodeInfo(int imgWidth, int imgHeight, char[] codeChars, int lineCout,float yawpRate) {
            this.imgWidth = imgWidth;
            this.imgHeight = imgHeight;
            this.codeChars = codeChars;
            this.lineCout = lineCout;
            this.yawpRate = yawpRate;
        }

        public BufferedImage getBufferedImage() {
            return bufferedImage;
        }

        public void setBufferedImage(BufferedImage bufferedImage) {
            this.bufferedImage = bufferedImage;
        }

        public char[] getCodeChars() {
            return codeChars;
        }
    }



    public static void write(String path) throws IOException {
        BufferedImage buffImg = createValidateCode(new CodeBuilder()).getBufferedImage();
        OutputStream sos = new FileOutputStream(path);
        write(sos,buffImg);
    }

    public static void write(OutputStream sos,BufferedImage buffImg) throws IOException {
        ImageIO.write(buffImg, "png", sos);
        sos.close();
    }


    public static  class CodeBuilder {
        private int imgWidth = ValidateCode.DEF_CODE_WIDTH;
        private int imgHeight = ValidateCode.DEF_CODE_HEIGHT;
        private int codeCount = ValidateCode.DEF_CODE_COUNT;
        private int lineCout = ValidateCode.DEF_LINE_COUNT;
        private float yawpRate = ValidateCode.DEF_YAWP_RATE;

        public int getImgWidth() {
            return imgWidth;
        }

        public CodeBuilder setImgWidth(int imgWidth) {
            this.imgWidth = imgWidth;
            return this;
        }

        public int getImgHeight() {
            return imgHeight;
        }

        public CodeBuilder setImgHeight(int imgHeight) {
            this.imgHeight = imgHeight;
            return this;
        }

        public int getCodeCount() {
            return codeCount;
        }

        public CodeBuilder setCodeCount(int codeCount) {
            this.codeCount = codeCount;
            return this;
        }

        public int getLineCout() {
            return lineCout;
        }

        public CodeBuilder setLineCout(int lineCout) {
            this.lineCout = lineCout;
            return this;
        }

        public float getYawpRate() {
            return yawpRate;
        }

        public void setYawpRate(float yawpRate) {
            this.yawpRate = yawpRate;
        }

        public CodeInfo builder(){
            return new CodeInfo(imgWidth,imgHeight,createCodes(codeCount),lineCout,yawpRate);
        }
    }


    /**
     * 测试函数,默认生成到d盘
     * @param args
     */
    public static void main(String[] args) {
        try {
            IdWorker.initIdWorker(14);
            for(int i=0;i<100;i++){
                String path="C:\\temp\\vlicode\\"+IdWorker.getInstance().nextId()+".png";
                write(path);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
